home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
HPAVC
/
HPAVC CD-ROM.iso
/
PCASM2.ZIP
/
BAS2-2.DOC
< prev
next >
Wrap
Text File
|
1990-08-01
|
42KB
|
941 lines
The PC Assembler Tutor mmxviii
______________________
If you now load the user library along with BASIC, you can look
at the segments and offsets of the arrays. Here is a program with
a couple of arrays:
**********************************************
k% = 12000
DIM array1!(k%), array2!(k%), array3!(12000)
value1! = VARPTR (array1!(0))
value2! = VARPTR (array2!(0))
value3! = VARPTR (array3!(0))
PRINT value1!, value2!, value3!
**********************************************
Each array is 12001 * 4 (bytes) or 48004 bytes long. The first
two have been defined as dynamic, so they can be anywhere in
memory as long as they're after the start of DS. The third one is
static, so it must be entirely in DS. Here's the output:
68032 116064 6252
Remember, these offsets are not relative to the start of memory,
they are relative to the start of the DS segment. The DS segment
only goes up to offset 65535 so the first two are outside of DS
while the third one is totally inside (6252 + 48004 = 54006 which
is less than 65536). PTR86 is going to give us a SEGMENT:OFFSET
pair relative to the start of memory.{1} The form for the call
is:
CALL PTR86 (segment%, offset%, value!)
where value! is the number returned by VARPTR.
We'll add the following code to the bottom of the above program:
***************************************
CALL PTR86 ( seg1% , off1%, value1! )
CALL PTR86 ( seg2% , off2%, value2! )
CALL PTR86 ( seg3% , off3%, value3! )
PRINT seg1%, off1%
PRINT seg2%, off2%
PRINT seg3%, off3%
***************************************
What does the program print now?
68032 116064 6252
____________________
1. If you are using QuickBasic 4.0 or later this has become
simplified. You can just use VARSEG and VARPTR. These will give
you a segment offset pair which is usable in a subroutine call.
______________________
The PC Assembler Tutor - Copyright (C) 1990 Chuck Nelson
BASIC II - Interfacing BASIC With Assembler mmxix
___________________________________________
19125 0
22127 0
15263 12
PTR86 has calculated the segments. The first two offsets are 0
and the third one is 12. Even though the third array is in the DS
segment, PTR86 has recalculated the segment to find the highest
segment which contains the first byte of the data. If you want to
do a little calculation, you can figure out that while this
program was running, DS was set to segment 14873.
VARPTR and PTR86 can be used to do this calculation for any array
element, not just array(0). Here's VARPTR:
value1! = VARPTR (array1!(0))
value2! = VARPTR (array1!(196))
value3! = VARPTR (array1!(2781))
PRINT value1!, value2!, value3!
And here's the output:
68032 68816 79156
From now on, we will have VARPTR inside of the PTR86 call:
CALL PTR86 ( seg1% , off1%, VARPTR (array1!(0)) )
CALL PTR86 ( seg2% , off2%, VARPTR (array2!(0)) )
CALL PTR86 ( seg3% , off3%, VARPTR (array3!(0)) )
It is clearer and uses less space. Of course, we can use this for
any element in the array, not just the beginning of the array:
CALL PTR86 ( seg1% , off1%, VARPTR (array1!(0)) )
CALL PTR86 ( seg2% , off2%, VARPTR (array1!(5076)) )
CALL PTR86 ( seg3% , off3%, VARPTR (array1!(1983)) )
We now have all the ammunition we need to do a quicker disk
write. We can pass the offset address of any numeric data in the
DS segment, we can pass the string descriptor address of any
string, and we can pass the SEGMENT:OFFSET pair of any array
anywhere.
Before doing our disk write program, I need to say something
about subroutine calls. You notice that when I show a subroutine
call I am always showing what numeric type I am passing. There is
a reason for this. Let's step back from assembler for a minute
and do a BASIC program with a subroutine. Here's the program:
**********************************************************
floatA! = 27.925
floatB! = 16.96
integerA% = 300
integerB% = 140
The PC Assembler Tutor mmxx
______________________
CALL CheckTheNumbers (integerA%, integerB%, floatA!, floatB!)
PRINT floatA!, floatB!, integerA%, integerB%
END
SUB CheckTheNumbers ( int1%, int2%, flt1!, flt2!) STATIC
int1% = int1% + 7
int2% = int2% * 45
flt1! = flt1! + 19.0
flt2! = flt2! * 43.0
END SUB
***********************************************************
This gives the following output:
46.925 729.28 307 6300
This is nothing earthshaking. Now let's change one line in the
program:
CALL CheckTheNumbers (floatA!, floatB!, integerA%, integerB%)
In the call statement I have put single precision numbers where
the integers were and integers where the single precision numbers
were. Now let's look at the output:
Overflow.
ENTER to debug, SPACEBAR to edit
We had an overflow. Here's where the debugger said the error was:
int2% = int2% * 45
But int2% received the floating point number for floatB!, and
that is 16.96. Since 16.96 * 45 = 763.2, where was the overflow?
What the subroutine saw was not 16.96 but the first two binary
bytes of floatB! (since we passed the address of floatB!). It
thought these were an integer, performed a multiplication and got
an error because the result was too big for an integer. Now,
change the value in floatB! to:
floatB! = 1.696E-29
and run the program again. Your results should be:
27.92501 1.693756E-29 0 16792
These numbers have no relation to either what we started out with
or what we did. Why? Because the subroutine mixed binary
information from single precision numbers with binary information
from integers and came up with unmitigated garbage. It was not
only doing that, it was also writing 4 bytes of information into
a 2 byte integer. Both flt1! and flt2! were writing past the end
of the data and overwriting something else.
There is NEVER any checking between a subroutine and the calling
program to see that the correct numeric types are being passed
(integers, long integers, single precision, double precision). In
BASIC II - Interfacing BASIC With Assembler mmxxi
___________________________________________
C and Pascal this checking is done by the compiler at compile
time and the compiler will howl if you try to do something like
this. In BASIC (my BASIC at least), this checking is not being
done. Therefore, it is IMPERATIVE that you make sure that you
pass the correct data types.
Having given that warning, we are going to build an assembler
subprogram that opens a file for writing, then writes a block of
data from memory to disk. The form of the call will be:
CALL BlockToDisk ("filename$"+CHR$(0), seg%, offset%, # of bytes)
Notice that there MUST be a 0 after the filename. When you use
this function, you must always have:
filename$ = "my.file.name" + CHR$(0)
The disk interrupt that is used in this subroutine expects a 'C'
string (terminated by a number 0, not an ASCII character '0'). If
you don't do it, you will almost certainly get an error.
This is followed by the segment of the first byte of data, the
offset of the first byte of data, and the number of BYTES (not
array elements) to write.
Which way does BASIC load the arguments to a subroutine? From
left to right, just like PASCAL. Also, in BASIC, ALL subroutine
calls are far calls. Therefore, BASIC will do the following when
it calls BlockToDisk:
PUSH address of file_descriptor
PUSH address of block segment
PUSH address of block offset
PUSH address of length
CALL FAR PTR BlockToDisk
There are two things to notice here. First, these are all
ADDRESSES of the data, not the data items themselves. Upon entry
to the subroutine and initialization of BP, the stack will look
like this:
address of file_descriptor bp+12
address of block segment bp+10
address of block offset bp+8
address of length bp+6
old CS bp+4
old IP bp+2
BP-> old BP bp
Secondly, the name of the subroutine does not have any periods.
BASIC allows periods '.' but does not allow underscores '_' while
assembler allows underscores but doesn't allow periods. (Periods
have a special meaning in assembler; they are used in
structures).
In order to go on from here, you need a book about interrupts.
This information is from "DOS Programmer's Reference" by Terry
The PC Assembler Tutor mmxxii
______________________
Dettmann, but if you have "The Peter Norton Programmer's Guide to
The IBM PC", that's fine too. I'm going to give only partial
information about these interrupts and you should have complete
information.
Here's the program. The explaination will come afterwards.
*******************************************************
; BASOUT.ASM
include \pushregs.mac
PUBLIC BLOCKTODISK
DGROUP GROUP _DATA
; - - - - - - - - - - - - - - - - - - - - -
_DATA SEGMENT PUBLIC 'DATA'
file_handle dw ?
error_message db "Disk i/o error #"
error_byte db " ", 13, 10, "$"
_DATA ENDS
; - - - - - - - - - - - - - - - - - - - - -
_TEXT SEGMENT 'CODE'
ASSUME cs:_TEXT, ds:DGROUP
; - - - - - - - - - - - - - - - - - - - - -
print_error proc far
mov ah, 9 ; print error message
mov dx, offset DGROUP:error_message
int 21h
ret
print_error endp
; - - - - - - - - - - - - - - - - - - - - - - - - -
; BlockToDisk ( filename , array SEG, array OFF, # of bytes)
; this is for BASIC
DESCRIPTOR_LOCATION EQU [bp + 12]
SEGMENT_LOCATION EQU [bp + 10]
OFFSET_LOCATION EQU [bp + 8]
LENGTH_LOCATION EQU [bp + 6]
BLOCKTODISK proc far
push bp
mov bp, sp
PUSHREGS ax, bx, cx, dx, si, ds
; open a new file or truncate an old one
mov si, DESCRIPTOR_LOCATION
mov ah, 3Ch ; open new or truncate old
mov cx, 0 ; normal file attribute
mov dx, [si+2] ; [si] =length, [si+2] =location
int 21h
jnc write_the_file ; ok if CF=0, error if CF=1
mov error_byte, '1' ; cannot open
call print_error
jmp exit
write_the_file:
mov file_handle, ax ; store handle for later use
mov bx, ax ; file_handle to bx
BASIC II - Interfacing BASIC With Assembler mmxxiii
___________________________________________
mov ah, 40h ; int 21h ah = 40h, write block
mov si, LENGTH_LOCATION
mov cx, [si] ; # of bytes into CX
push ds ; save BASIC's DS
mov si, OFFSET_LOCATION
mov dx, [si] ; offset to DX
mov si, SEGMENT_LOCATION
mov ds, [si] ; segment to DS
int 21h
pop ds ; restore BASIC's DS
jnc normal_exit ; ok if CF=0, error if CF=1
mov error_byte, '2' ; bad file write
call print_error
normal_exit:
mov ah, 3Eh ; close the file
mov bx, file_handle
int 21h
exit:
POPREGS ax, bx, cx, dx, si, ds
mov sp, bp
pop bp
ret (8) ; pop 4 words (8 bytes off the stack)
BLOCKTODISK endp
; - - - - - - - - - - - - - - - - - - - - - - - - -
_TEXT ENDS
END
**************************************************************
This is using the standardized segment names so the data will be
in the DS segment. Notice the DGROUP GROUP declaration. Also
notice that in the 'print_error' subroutine, we have done an
offset override with 'offset DGROUP:error_message'. You need to
do this every time to avoid errors with the offset addressing
whenever you are using the DGROUP GROUP directive. Go back to the
discussion of simplified segment directives if you don't remember
this.
The main program has 3 interrupts. The first one opens a new file
or truncates an old one to zero length. The file will be usable
for reading and/or writing:
Int 21h function 3Ch
AH = 3Ch
CX = 0 0 indicates a normal file
DS:DX address of ASCII filename (terminated by 0)
Returns:
AX = file handle if CF = 0
or: AX = error code if CF = 1
This filename can be any legitimate pathname specification, and
must be terminated by a zero. Like all the disk interrupts we
will see in this chapter, if there is an error, the interrupt
will set CF = 1. Otherwise it will clear CF = 0. If CF = 0 you
The PC Assembler Tutor mmxxiv
______________________
can go on; if CF = 1, there was an error and you need to
terminate the subroutine and do some error reporting. The file
handle is a number from 0 to 65535 which the operating system
gives your program to uniquely identify that open file. There is
no other open file in the system which has that number. Guard it
carefully because it is your ONLY access to the file.
The second interrupt writes a block of data to disk.
Int 21h function 40h
AH = 40h
BX = file handle
CX = number of bytes to write
DS:DX = address of first byte of data
Returns:
AX = actual number of bytes written if CF = 0
AX = error code if CF = 1
This too sets the carry flag if there was an error and clears it
if there wasn't. It is limited to writing 65535 bytes at a time,
but the largest array we can have is 65535 bytes (actually 65534
since all data types have an even number of bytes), so this is no
problem.
The third interrupt closes the file.
Int 21h function 3Eh
AH = 3Eh
BX = File handle
Also, the print-error subroutine has an interrupt
Int 21h function 09h
AH = 9
DS:DX = first byte of string.
This string must be terminated by a dollar sign '$' (of all
things). The message is on two lines so we can insert an error
number into the middle of the message. This is a quick and dirty
interrupt for string printing.
All interrupt numbers and function numbers are hex. This is
standard for interrupts. If things go wierd, always check first
to make sure that you have a hex number and not a decimal number.
The data has an 'ASSUME ds:DGROUP' statement.
Like Pascal, BASIC requires that the CALLED subroutine pop the
arguments off the stack, so we pop 4 extra words (8 extra bytes)
with:
ret (8)
Assemble this program and put the object file in a library with
the other object files by using BUILDLIB.EXE. Now all we need is
a BASIC program to use this. Here it is:
BASIC II - Interfacing BASIC With Assembler mmxxv
___________________________________________
************************************************************
DIM large.array! (10000)
FOR i% = 1 to 10000
large.array! (i%) = 2.167832E+19
NEXT i%
filename$ = "blocktxt.doc" + CHR$ (0)
length% = 40000 - 65536
PRINT time$
CALL PTR86 (segment%, offset%, VARPTR (large.array!(1)) )
CALL BlockToDisk ( filename$ , segment% , offset%, length% )
PRINT
PRINT time$
************************************************************
There is an extra PRINT statement there which I will explain
later. We are starting at large.array!(1) because that is where
we started with the other programs. why are we subtracting 65536?
Because BASIC has a limit of -32768 to +32767, so we store 40000
as its modular equivalent (mod 65536).
How long does the disk write take? From 2 to 3 seconds, and much
of that time was spent opening and truncating the file. This is
significantly better than the other ways of doing i/o. In fact,
the limits of this routine are the limits of your system. It is
literally impossible to do disk i/o any faster than this.
Try using a filename that doesn't have a CHR$(0) at the end. You
should get an error message. In my BASIC, here is the output:
19:50:23
Disk i/o error #1
19:50:23
Now remove that lone PRINT statement (the next to the last line).
Here's my output:
19:50:00
D9:50:00 error #1
1
For the QuickBASIC 3.0 environment, BASIC thinks that it has
complete control of screen i/o, so it is not doing its i/o in a
standard way and is overwriting the error message. If you are
going to do any screen i/o from assembler, you will have to think
of a way to live in harmony with BASIC.{2} We simply trick BASIC
into writing an empty line where the message was. This may not
always work correctly, especially if the window is scrolling up.
____________________
2. The easiest way to do this is to save the whole screen
image and cursor location, do what you want using the whole
screen, and then restore the screen and the cursor before
returning.
The PC Assembler Tutor mmxxvi
______________________
This whole program only involved interrupts. There is nothing
intrinsically assembler-like in its capabilities. In fact, we'll
do its disk read counterpart entirely in BASIC.
Let's do something that requires assembler language. The BASIC
program:
*******************************************
FOR i% = 1 to 10000
to.array!(i%) = from.array!(i%)
NEXT i%
*******************************************
get's the job done, but is isn't all that fast. It requires about
5.5 seconds. This is a natural for assembler. Dive down into the
assembler level, move the string, and come back up for air. Our
BASIC program will be:
*******************************************
n% = 10000
DIM from.array! (n%), to.array! (n%)
PRINT time$
FOR i% = 1 to 10000
to.array!(i%) = from.array!(i%)
NEXT i%
PRINT time$
FOR j% = 1 to 50
cnt% = 40000 - 65536 'count
CALL PTR86 (from.seg%, from.off%, VARPTR( from.array!(1)) )
CALL PTR86 (to.seg%, to.off%, VARPTR ( to.array!(1)) )
CALL BlockMove(from.seg%, from.off%, to.seg%, to.off%, cnt%)
NEXT j%
PRINT time$
********************************************
We are doing 50 repeats of the bottom section of code so you will
be able to average the time. Here's the assembler program:
; - - - - - - - - - -
include /pushregs.mac
_TEXT SEGMENT PUBLIC 'CODE'
ASSUME cs:_TEXT
PUBLIC BlockMove
; - - - - - - - - - -
; BlockMove ( from.seg, from.off, to.seg, to.off, byte.count)
; for BASIC
; MOVSW is from DS:[SI] to ES:[DI]
FROM_SEG_ADDRESS EQU [bp+14]
FROM_OFFSET_ADDRESS EQU [bp+12]
TO_SEG_ADDRESS EQU [bp+10]
TO_OFFSET_ADDRESS EQU [bp+8]
BYTE_COUNT_ADDRESS EQU [bp+6]
; - - - - - - - - - -
BlockMove proc far
push bp
mov bp, sp
BASIC II - Interfacing BASIC With Assembler mmxxvii
___________________________________________
PUSHREGS ax, bx, cx, dx, si, di, es, ds
mov si, TO_SEG_ADDRESS
mov es, [si] ; to_seg to ES
mov si, TO_OFFSET_ADDRESS
mov di, [si] ; to_offset to DI
mov si, BYTE_COUNT_ADDRESS
mov cx, [si] ; byte count to CX
mov si, FROM_SEG_ADDRESS
mov ax, [si] ; temporary storage for new DS
mov si, FROM_OFFSET_ADDRESS
mov si, [si] ; from_offset to SI
mov ds, ax ; now move from_seg to DS
sub bx, bx ; clear BX
shr cx, 1 ; divide by 2, remainder in CF
rcl bx, 1 ; move CF to low bit of BX
cld ; clear DF (go up)
rep movsw ; the block move (count in CX)
and bx, bx ; one extra byte?
jz exit
movsb ; move one last byte
exit:
POPREGS ax, bx, cx, dx, si, di, es, ds
mov sp, bp
pop bp
ret (10)
BlockMove endp
; - - - - - - - - - -
_TEXT ENDS
END
; - - - - - - - - - -
This is a string block move using MOVSW. The count is the number
of BYTES, not the number of array elements. CX contains the byte
count. It is divided by 2 so we can move words, and if there is a
remainder (i.e. if the number was odd), BX is set to 1. We move
words instead of bytes, and afterwards we check BX to see if we
need to move 1 byte more. This routine takes about 1/8 second
instead of 5.5 seconds. This is a considerable savings in time.
There is a small problem, however. If the FROM block and the TO
block overlap (e.g. move 400 bytes from array!(11) to
array!(26)), then the data may be compromised. To be exact, if
the start of the FROM data is below the start of the TO data, the
data will be screwed up. The general solution of this for BASIC
is in BLKMOVE.ASM, which is in a file called MISHMASH.DOC which
is in \XTRAFILE.
Finally, here's the disk read done entirely in BASIC. Once again
you need your DOS interrupt book.
********************************************************
' READBLK.BAS
' reads a block from the disk into memory
DIM in.regs%(9), out.regs%(9)
The PC Assembler Tutor mmxxviii
______________________
DIM big.array! (10000)
AX% = 0
BX% = 1
CX% = 2
DX% = 3
BP% = 4
SI% = 5
DI% = 6
FLGS% = 7
DS% = 8
ES% = 9
filename$ = "blocktxt.doc" + CHR$(0)
PRINT time$
' open an existing file for reading
in.regs%(AX%) = &H3D00
in.regs%(DX%) = SADD (filename$)
CALL INT86 (&H21,VARPTR (in.regs%(0)),VARPTR (out.regs%(0)))
IF (out.regs%(FLGS%) AND &H0001) <> 0 THEN
PRINT "Can't open the file."
GOTO ExitProgram
END IF
' set the i/o pointer to 0
file.handle% = out.regs%(AX%)
in.regs%(AX%) = &H4200
in.regs%(BX%) = file.handle%
in.regs%(CX%) = 0
in.regs%(DX%) = 0
CALL INT86 (&H21, VARPTR(in.regs%(0)), VARPTR(out.regs%(0)))
IF (out.regs%(FLGS%) AND &H0001) <> 0 THEN
PRINT "File pointer error"
GOTO CloseFile
END IF
in.regs%(AX%) = &H3F00
in.regs%(BX%) = file.handle%
in.regs%(CX%) = 40000 - 65536
CALL PTR86 ( segment%, offset%, VARPTR (big.array!(1) ))
in.regs%(DX%) = offset%
in.regs%(DS%) = segment%
CALL INT86X (&H21,VARPTR(in.regs%(0)),VARPTR(out.regs%(0)))
IF (out.regs%(FLGS%) AND &H0001) <> 0 THEN
PRINT "Disk read error"
END IF
CloseFile:
in.regs%(AX%) = &H3E00
in.regs%(BX%) = file.handle%
CALL INT86 (&H21, VARPTR(in.regs%(0)), VARPTR(out.regs%(0)))
PRINT time$
BASIC II - Interfacing BASIC With Assembler mmxxix
___________________________________________
ExitProgram:
END
******************************************************
This shows the use of INT86 in BASIC. We have two 10 element
integer arrays (0 - 9). One is used for putting the data into the
registers before the interrupt call and the other is used for
getting the data out of the registers after the interrupt. At the
top we have substituted variable names for the array elements
they represent. This is the only way to make sense of things in
BASIC. What is the difference between INT86 and INT86X? INT86X
also changes ES and DS.
We need a different file opening call because the last one
TRUNCATED the file. This one just opens a pre-existing file and
we put 00 in AL to signal a file read:
INT 21h Function 3Dh
AH = 3dh ; open a pre-existing file
AL = 0 ; file read
DS:DX ; pointer to a 00h terminated string
Returns
AX = file handle if CF = 0
AX = error code if CF = 1
We use SADD to get the filename offset in DS because this is
exactly what DOS wants. SADD is a function that gives the offset
of a string (the string itself) relative to the DS segment. It
gives no length information.
At every step along the way we check CF to make sure it is 0 and
not 1. We need to make sure that the file pointer is at the
beginning of the file. This is:
INT 21h Function 42h
AH = 42h ; move file pointer
AL = 0 ; count from beginning of the file
CX:DX = 0 ; 4 byte offset from beginning of file
Returns
DX:AX = new file-pointer location if CF = 0
AX = error code if CF = 1
Then we do the block read:
INT 21h Function 3Fh
AH = 3Fh ; read a block from disk
BX = file handle
CX = byte count
DS:DX = pointer to first byte of block
Returns
AX = # of bytes read (can be less than CX) if CF = 0
AX = error code if CF = 1
The PC Assembler Tutor mmxxx
______________________
Finally, we close the file:
INT 21h Function 3Eh
AH = 3Eh ; close a file
BX = file handle
Returns
nothing if CF = 0 (close was successful)
AX = error code if CF = 1
All you need to know about CF is that when the flags are
represented as a word (2 bytes) CF = &H0001.
Is this faster than our other access methods? Our worst case
before took 79 seconds and this one takes 1 second. This is
certainly worth using for large disk reads. We don't need to go
down to the assembler level, either.
What's the difference between this and bload? Bload requires that
the file has already been stored from memory. When you use BSAVE,
the binary information is written to disk, but the first seven
bytes is BLOAD information. The first byte (0FDh) is a signature
byte. The next six bytes are three words. (1) the segment where
the data came from, (2) the offset where the data came from, and
(3) the length of the data. Of course, having these seven bytes
in the front makes the file incompatible with everything else in
the world. It even makes it difficult to load the information
into BASIC the first time, since this seven byte header is
missing in the original data unless the data came from a BASIC
file.
So what sorts of things are candidates for assembler subroutines?
Things that are cumbersome in BASIC. If you want the top byte of
a number, that's difficult. If you want to rotate the bits of a
number, that's extremely hard. Shifting bits is hard. Practically
everything involving unsigned numbers is problematic. For every
assembler instruction, if you can't do it easily in BASIC you
should make a subroutine that does it in assembler. How about one
that does unsigned division? Another subroutine that you might
want to make is one that returns both the quotient and the
remainder from signed division. This cuts the work in half if you
need both of them.
Well, use BASIC any way you want, but most of all, have fun!
BASIC II - Interfacing BASIC With Assembler mmxxxi
___________________________________________
SUMMARY
BASIC strings are defined by STRING DESCRIPTORS. A string
descriptor is a 4 byte block that contains the LENGTH of the
string and its LOCATION in the DS segment. Though you may modify
individual bytes of a string from the assembler level, you may
not alter the length without interfering with BASIC's memory
management system.
BASIC passes all arguments by reference. That is it sends the
offset address of the data instead of the data itself. The ruiles
are:
1) If it is a single piece of numeric data, the offset is
relative to the DS segment.
2) If it is a string, the address is the address of the
STRING DESCRIPTOR which contains both the length and
location of the string.
3) To reference an array, use VARPTR (array(0)) to get the
offset and then PTR86 to convert this to a SEGMENT:OFFSET
pair which is usable by the assembler subroutine.
4) If you pass a single array element array(x) instead of
using VARPTR, BASIC will pass the location of that element,
but the element might be separated from the rest of the
array, so only pass an individual element if you want the
element itself and not the array.{3}
SADD (stringname$)
SADD [Microsoft's string address function] is used to pass
the offset address of stringname$ relative to BASIC's DS
segment. It should only be used with 00h terminated strings
since this gives no length information. It can, however, be
used in conjunction with LEN (stringname$).
number! = VARPTR (variable)
In older BASICs, VARPTR gives the offset address of
"variable" relative to the first byte of the DS segment.
This variable can be anywhere in memory from DS:0000 to the
end of memory, and the number returned will be a single
precision number in the range 0 to 1,048,576.
VARSEG and VARPTR
In more recent BASICs, using a combination of VARSEG and
____________________
3. The rule here is that if the array itself is outside of the
DS segment (if it is a dynamic array), BASIC will make a copy of
the element inside of DS before the CALL, give you the address of
the COPY, and return the copy to its appropriate place in the
array after the CALL. This copy can be hundreds of thousands of
bytes away from the actual array. If you want the element itself
this works properly, but if you want the array, the address will
be the wrong address.
The PC Assembler Tutor mmxxxii
______________________
VARPTR has supplanted the use of PTR86.
PTR86 ( segment%, offset%, VARPTR (variable) )
PTR86 [Microsoft's segmentation scheme] takes the result
provided by VARPTR and adds it to DS to come up with a total
address. It then converts this absolute address into a
SEGMENT:OFFSET pair where the segment is the highest segment
that contains the first byte of the variable from VARPTR and
the offset is a number from 0 to 15 which is the offset of
this variable in this segment.
RET (x)
When executing a return, all called subroutines must pop the
arguments passed to them by BASIC. The number of BYTES
popped is twice the number of arguments (as long as you are
passing addresses and not actual data).
CALL MY_ROUTINE (arg1, arg2, arg3, etc)
Arguments are always PUSHed on the stack from left to right,
so this call will:
PUSH address of arg1
PUSH address of arg2
PUSH address of arg3
etc.
in that order.
INT86 ( interrupt.number%, in.reg.array%(9), out.reg.array%(9) )
INT86 executes a DOS interrupt (interrupt.number%). The
integers in in.reg.array% are put into the arithmetic
registers before the call and the arithmetic registers are
put into out.reg.array after the call. INT86X does the same
thing but also changes the DS and ES segment registers.
Consult your BASIC manual for the proper ordering of the
registers in the array. Neither of these changes CS, SS or
SP.